﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace System.Data.Entity.Repository.Filters
{
    public static class QueryableFilterManager
    {
        internal static ConcurrentDictionary<string, RegisterableFilter> filters = new ConcurrentDictionary<string, RegisterableFilter>();

        public static void ClearFilters()
        {
            filters = null;
            filters = new ConcurrentDictionary<string, RegisterableFilter>();
        }

        public static void Register(RegisterableFilter filter)
        {
            string typeName = filter.FilterInstance.GetType()
                .FullName;

            filters.TryAdd(typeName, filter);
        }

        public static IQueryable<T> FilterWith<Q, T>(this IQueryable<T> query, params Type[] disableFilters)
        {
            string typeName = typeof(Q).FullName;

            IQueryable<T> result = query;

            ConcurrentDictionary<string, RegisterableFilter> runnableFilters = new ConcurrentDictionary<string, RegisterableFilter>();
            filters.ToList().ForEach(x => runnableFilters.TryAdd(x.Key, x.Value));

            //If there are any filters we want to disable than remove them from the runnable filters list before proceeding
            foreach (var disabledFilter in disableFilters)
            {
                var filtersToRemove = runnableFilters.Where(r => r.Value.FilterInstance.GetType().Name == disabledFilter.Name);

                foreach (var filter in filtersToRemove)
                {
                    RegisterableFilter removedFilter;
                    runnableFilters.TryRemove(filter.Key, out removedFilter);
                }
            }

            foreach (var filter in runnableFilters.Where(f => f.Value.FilterInstance.GetType()
                .GetInterfaces()
                .Contains(typeof(Q))))
            {
                result = (filter.Value.FilterInstance as IQueryableFilter).Filter(query, filter.Value.ParameterBag);
            }

            foreach (var filter in runnableFilters.Where(f => f.Value.FilterInstance.GetType()
                .GetInterfaces()
                .Contains(typeof(IGlobalQueryFilter))))
            {
                result = (filter.Value.FilterInstance as IQueryableFilter).Filter(query);
            }

            CleanupTransientFilters();

            return result;
        }

        public static IQueryable<T> FilterWith<T>(this IQueryable<T> query, Type[] withFilters, params Type[] disableFilters)
        {
            IQueryable<T> result = query;

            ConcurrentDictionary<string, RegisterableFilter> runnableFilters = new ConcurrentDictionary<string, RegisterableFilter>();
            filters.ToList().ForEach(x => runnableFilters.TryAdd(x.Key, x.Value));

            //If there are any filters we want to disable than remove them from the runnable filters list before proceeding
            foreach (var disabledFilter in disableFilters)
            {
                var filtersToRemove = runnableFilters.Where(r => r.Value.FilterInstance.GetType().Name == disabledFilter.Name);

                foreach (var filter in filtersToRemove)
                {
                    RegisterableFilter removedFilter;
                    runnableFilters.TryRemove(filter.Key, out removedFilter);
                }
            }

            foreach (var withFilter in withFilters)
            {
                foreach (var filter in runnableFilters.Where(f => f.Value.FilterInstance.GetType()
                   .GetInterfaces()
                   .Contains(withFilter)))
                {
                    result = (filter.Value.FilterInstance as IQueryableFilter).Filter(query, filter.Value.ParameterBag);
                }
            }

            CleanupTransientFilters();

            return result;
        }

        public static bool UnRegister<Q>()
        {
            Type type = typeof(Q);
            bool isInterface = type.IsInterface;
            string typeName = type.FullName;

            if (isInterface)
                return UnRegisterInterface<Q>();

            return UnregisterTypedFilter(type);
        }

        internal static bool UnregisterTypedFilter(Type filterType)
        {
            bool result = false;

            string typeName = filterType.FullName;

            foreach (var filter in filters)
            {
                if (filter.Key == typeName)
                {
                    RegisterableFilter removedFilter;
                    filters.TryRemove(filter.Key, out removedFilter);

                    if (removedFilter != null)
                        result = true;
                }
            }

            return result;
        }

        //public static bool UnRegister(params Type[] unregisterFilters) 
        //{
        //    bool result = false;

        //    foreach (var unregisterFilter in unregisterFilters)
        //    {
        //        var filtersToRemove = filters.Where(r => r.Value.GetType().Name == unregisterFilter.Name);

        //        foreach (var removefilter in filtersToRemove)
        //        {
        //            IQueryableFilter removedFilter;
        //            filters.TryRemove(removefilter.Key, out removedFilter);

        //            if (removedFilter != null)
        //                result = true;
        //        }
        //    }

        //    return result;
        //}

        internal static bool UnRegisterInterface<T>()
        {
            bool result = false;

            foreach (var filter in filters.Where(f => f.Value.FilterInstance.GetType()
               .GetInterfaces()
               .Contains(typeof(IGlobalQueryFilter))))
            {
                RegisterableFilter removedFilter;
                filters.TryRemove(filter.Key, out removedFilter);

                if (removedFilter != null)
                    result = true;
            }

            return result;
        }

        //List of filters to be removed
        public static bool UnRegister(params object[] filters)
        {
            bool result = false;

            foreach (var item in filters)
            {
                UnRegister(item);
            }

            return result;
        }

        public static void CleanupTransientFilters()
        {
            List<string> transientFilters = filters.Where(x => x.Value.LifeTimeScope == FilterLifeTimeScopes.Transient)
                .Select(f => f.Key)
                .ToList();

            RegisterableFilter removedFilter;
            transientFilters.ForEach(f => filters.TryRemove(f, out removedFilter));
        }
    }
}
